import base64
import io
import queue
import sqlite3
import threading
import time
import tkinter as tk
from collections import defaultdict
from tkinter import messagebox, filedialog
from tkinter import ttk

import pygame
import pyttsx3


class FlashTypingApp:
    def __init__(self, root):
        self.engine = pyttsx3.init()
        self.engine.setProperty('rate', 150)
        self.speech_queue = queue.Queue()  # 新增：用于管理语音任务的队列
        self.speech_thread = threading.Thread(target=self.process_speech_queue, daemon=True)
        self.speech_thread.start()  # 启动语音处理线程
        self.root = root
        self.root.title("打字练习")

        # 初始化数据库
        self.init_db()
        # 初始化声音配置
        self.init_sound_settings()
        # 从数据库加载默认提示语
        self.cursor.execute('SELECT value FROM settings WHERE key = "start_prompt"')
        result = self.cursor.fetchone()
        self.start_prompt = result[0] if result else "让我们开始练习打字吧"  # 如果数据库中没有设置,则使用默认值

        threading.Thread(target=self.speak_word, args=(self.start_prompt,)).start()

        # 初始化pygame混音器
        pygame.mixer.init()
        self.load_embedded_sound()

        # 错误统计
        self.error_stats = defaultdict(int)
        self.load_error_stats()

        self.default_text = """
In the dark we  we  we  在 黑暗中 我们 
# 在黑暗中我们 
stand apart we  we 
# 分开站立 我们 
never see that the things we need are staring right at us 
# 永远看不到我们需要的东西就在眼前 
You just want to hide  hide  hide 
# 你只是想逃避 逃避 
never show your smile  smile  smile 
# 从不展现你的笑容 笑容 
Stand alone when you need someone 
# 当你需要陪伴时 我独自一人 
it's the hardest thing of all 
# 这是最难的事情 
that you see are the bad  bad  bad  bad memories 
# 你所看到的都是糟糕的回忆 
take your time  and you'll find me 
# 别着急 你会找到我的 

I can see the sky  sky  sky 
# 我可以看到天空 天空 天空 
beautiful tonight  night 
# 今晚的天空如此美丽 
when you breathe why can't you see 
# 为什么当你呼吸时你不能看到 
the clouds are in your head 
# 你头上的乌云 
I will stay there there  there 
# 我会留在那里 那里 那里 
no need to fear  fear 
# 不需要害怕 害怕 
when you need to talk it out with someone you can trust 
# 当你需要和一个你可以信任的人倾诉时 
What you see are the bad  bad  bad 
bad memories 
# 你所看到的都是糟糕的回忆 
take your time  you'll find me 
# 别着急 你会找到我的 

I'll chase them all away 
# 我会把它们全部驱散 
You've got the chance to see the light 
# 你有机会看到希望的光芒 
even in the darkest night 
# 即使在最黑暗的夜晚 
And I will be here like you were for me 
# 我会像你曾经对我那样陪伴你 
so just let me in 
# 所以就让我进入你的内心吧 

I see your monsters 
# 我看见你的恐惧 
I see your pain 
# 我看见你的痛苦 
Tell me your problems 
# 告诉我你的烦恼 
I'll chase them away 
# 我会帮你驱散 
I'll be your lighthouse 
# 我会成为你的灯塔 
I'll make it okay 
# 我会让你感觉好一些 
When I see your monsters 
# 当我看到你的恐惧 
I'll stand there so brave 
# 我会勇敢地站在那里 
and chase them all away 
# 把它们全部驱散 
                """

        self.practice_text = self.default_text
        self.current_pos = 0
        self.skip_chars = {' ', '\t', '\n'}
        self.correct_count = 0
        self.wrong_count = 0
        self.current_char = ""
        self.blink_id = None
        self.flash_id = None
        self.blink_state = False

        # 记录练习开始时间
        self.start_time = time.time()

        # 设置全屏
        self.root.attributes('-fullscreen', True)
        self.root.bind("<F11>", self.toggle_fullscreen)
        self.root.bind("<Escape>", self.exit_fullscreen)
        self.root.bind("<Key>", self.handle_key_press)



        # 创建界面
        self.setup_ui()

    def init_db(self):
        """初始化SQLite数据库"""
        self.conn = sqlite3.connect('typing_stats.db')
        self.cursor = self.conn.cursor()

        # 创建错误统计表
        self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS error_stats (
                char TEXT PRIMARY KEY,
                count INTEGER DEFAULT 0
            )
        ''')

        # 创建字符输入统计表
        self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS char_stats (
                char TEXT PRIMARY KEY,
                total_input INTEGER DEFAULT 0
            )
        ''')

        # 创建练习文本表
        self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS practice_texts (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                content TEXT,
                timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
            )
        ''')

        # 新增：创建打字记录表,用于存储每次练习的完成时间
        self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS typing_records (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                elapsed_time REAL,
                timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
            )
        ''')

        # 新增：创建设置表,用于存储用户设置
        self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS settings (
                key TEXT PRIMARY KEY,
                value TEXT
            )
        ''')

        # 新增：创建声音设置表
        self.cursor.execute('''
            CREATE TABLE IF NOT EXISTS sound_settings (
                key TEXT PRIMARY KEY,
                value INTEGER DEFAULT 1
            )
        ''')
        self.conn.commit()

    def init_sound_settings(self):
        """初始化声音配置"""
        # 从数据库加载声音配置
        self.cursor.execute('SELECT key, value FROM sound_settings')
        sound_settings = {row[0]: bool(row[1]) for row in self.cursor.fetchall()}
        self.enable_key_sound = sound_settings.get('enable_key_sound', True)
        self.enable_word_reading = sound_settings.get('enable_word_reading', True)

    def load_error_stats(self):
        """从数据库加载错误统计"""
        self.cursor.execute('SELECT char, count FROM error_stats')
        for char, count in self.cursor.fetchall():
            self.error_stats[char] = count

    def save_error_stat(self, char):
        """保存错误统计到数据库"""
        self.cursor.execute('''
            INSERT OR REPLACE INTO error_stats (char, count)
            VALUES (?, COALESCE((SELECT count FROM error_stats WHERE char = ?), 0) + 1)
        ''', (char, char))
        self.conn.commit()

    def save_char_input(self, char):
        """保存字符输入统计"""
        self.cursor.execute('''
            INSERT OR REPLACE INTO char_stats (char, total_input)
            VALUES (?, COALESCE((SELECT total_input FROM char_stats WHERE char = ?), 0) + 1)
        ''', (char, char))
        self.conn.commit()

    def clear_stats(self):
        """清除所有统计数据和错误率记录"""
        # 重置内存中的统计
        self.correct_count = 0
        self.wrong_count = 0
        self.error_stats = defaultdict(int)
        # 更新字符统计显示
        self.load_and_display_char_stats()
        # 重置数据库中的统计
        self.cursor.execute('DELETE FROM error_stats')
        self.cursor.execute('DELETE FROM char_stats')
        self.conn.commit()

        # 重置UI显示
        self.status_label.config(text="统计已重置,按下键盘开始练习...", foreground="gray")
        self.stats_label.config(text="正确: 0 | 错误: 0 | 进度: 0%")

        # 清除所有高亮标记
        self.text_preview.config(state=tk.NORMAL)
        self.text_preview.tag_remove("error_highlight", "1.0", tk.END)
        self.text_preview.config(state=tk.DISABLED)

        # 重置练习位置
        self.current_pos = 0
        self.update_text_preview()
        threading.Thread(target=self.speak_word, args=("所有统计数据已清除",)).start()
        messagebox.showinfo("成功", "所有统计数据已清除！")

    def save_practice_text(self, text):
        """保存练习文本到数据库"""
        self.cursor.execute('INSERT INTO practice_texts (content) VALUES (?)', (text,))
        self.conn.commit()
    #键盘音效
    def load_embedded_sound(self):
        sound_base64 = """
        SUQzAwAAAABCMVREQVQAAAAFAAAAMTIwM1RJTUUAAAAFAAAAMTQ1N1BSSVYAABj3AABYTVAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS41LWMwMjEgNzkuMTU1MjQxLCAyMDEzLzExLzI1LTIxOjEwOjQwICAgICAgICAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiCiAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgICB4bWxuczp4bXBETT0iaHR0cDovL25zLmFkb2JlLmNvbS94bXAvMS4wL0R5bmFtaWNNZWRpYS8iCiAgICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICB4bWxuczpiZXh0PSJodHRwOi8vbnMuYWRvYmUuY29tL2J3Zi9iZXh0LzEuMC8iCiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OWI0MWNhNTMtNTM4Yi00Y2ViLTk3YjctZmE1YzI3Y2EzZmZkIgogICB4bXBNTTpEb2N1bWVudElEPSJlMTM1YjgwNS1kMjMzLTQwZWYtZTM1OC1iZWQ0MDAwMDAwNDkiCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowNGVlMTgyNi05N2MwLTRiZjgtOTU4Yi0xNjJmMDExMzIwMjIiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMTQtMDMtMTJUMTQ6NTc6NTEtMDQ6MDAiCiAgIHhtcDpNb2RpZnlEYXRlPSIyMDE0LTAzLTEyVDE0OjU3OjUxLTA0OjAwIgogICB4bXA6Q3JlYXRlRGF0ZT0iMjAxNC0wMy0xMlQxNDo1NzoyMS0wNDowMCIKICAgeG1wRE06YXVkaW9TYW1wbGVSYXRlPSI0NDEwMCIKICAgeG1wRE06YXVkaW9TYW1wbGVUeXBlPSIxNkludCIKICAgeG1wRE06YXVkaW9DaGFubmVsVHlwZT0iU3RlcmVvIgogICB4bXBETTpzdGFydFRpbWVTY2FsZT0iMzAwMDAiCiAgIHhtcERNOnN0YXJ0VGltZVNhbXBsZVNpemU9IjEwMDEiCiAgIGRjOmZvcm1hdD0iTVAzIgogICBiZXh0OmRlc2NyaXB0aW9uPSJPRkZJQ0UgQ09NUFVURVIgS0VZQk9BUkQgUFJFU1MgU0lOR0xFIEtFWSBIQVJEIDAxIgogICBiZXh0Om9yaWdpbmF0b3I9IkFkb2JlIFN5c3RlbXMgSW5jIgogICBiZXh0Om9yaWdpbmF0aW9uRGF0ZT0iMjAxNC0wMy0wNSIKICAgYmV4dDpvcmlnaW5hdGlvblRpbWU9IjIwOjAyOjM0IgogICBiZXh0OnRpbWVSZWZlcmVuY2U9IjAiCiAgIGJleHQ6dmVyc2lvbj0iMSI+CiAgIDx4bXBNTTpIaXN0b3J5PgogICAgPHJkZjpTZXE+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InNhdmVkIgogICAgICBzdEV2dDppbnN0YW5jZUlEPSIzOTNkODlmYy00YzI1LTZlZWItOWQxYi0yMGMxMDAwMDAwNzYiCiAgICAgIHN0RXZ0OndoZW49IjIwMTQtMDMtMTJUMTQ6NTc6NTEtMDQ6MDAiCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIEFkb2JlIE1lZGlhIEVuY29kZXIgQ0MgKE1hY2ludG9zaCkiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9IjMzNmM3Y2Q0LWJmMTQtODNkOS1iMzE0LWM5OWUwMDAwMDA3NiIKICAgICAgc3RFdnQ6d2hlbj0iMjAxNC0wMy0wNVQyMDowMjozNC0wNTowMCIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgQWRvYmUgTWVkaWEgRW5jb2RlciBDQyAoTWFjaW50b3NoKSIKICAgICAgc3RFdnQ6Y2hhbmdlZD0iLyIvPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJzYXZlZCIKICAgICAgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpDMjRDQ0YwRTk1MjA2ODExOTEwOUIzQjFEMUZCNUQxNCIKICAgICAgc3RFdnQ6d2hlbj0iMjAxMy0wNC0zMFQxMTo0MToyMC0wNDowMCIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgQWRvYmUgTWVkaWEgRW5jb2RlciA1LjUuMCIKICAgICAgc3RFdnQ6Y2hhbmdlZD0iL21ldGFkYXRhOy9jb250ZW50Ii8+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InNhdmVkIgogICAgICBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjFCMUNDODI0OTkyMDY4MTE5MTA5QjNCMUQxRkI1RDE0IgogICAgICBzdEV2dDp3aGVuPSIyMDEzLTA0LTMwVDEyOjEwOjM1LTA0OjAwIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBBZG9iZSBNZWRpYSBFbmNvZGVyIDUuNS4wIi8+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249Im1vZGlmaWVkIgogICAgICBzdEV2dDpwYXJhbWV0ZXJzPSJ1bmtub3duIG1vZGlmaWNhdGlvbnMiLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9ImJkZDc2N2M1LWVjZmYtNGFiNS03ZTgxLTZhMzQwMDAwMDA3NiIKICAgICAgc3RFdnQ6d2hlbj0iMjAxNC0wMy0wNVQxOTozNDowNi0wNTowMCIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgQWRvYmUgTWVkaWEgRW5jb2RlciBDQyAoTWFjaW50b3NoKSIKICAgICAgc3RFdnQ6Y2hhbmdlZD0iLyIvPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJzYXZlZCIKICAgICAgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDo4ZjQxMjU1OS1iZWY1LTQ3MjgtYWU5Mi1jYzFhNDdjMjMyNWQiCiAgICAgIHN0RXZ0OndoZW49IjIwMTQtMDMtMDVUMjA6MDI6MzQtMDU6MDAiCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIEFkb2JlIE1lZGlhIEVuY29kZXIgQ0MgKE1hY2ludG9zaCkiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6M2Y5NzMyNTAtMDNmOS00YjhhLTgyNDUtODA3Y2QzMjc4NjQwIgogICAgICBzdEV2dDp3aGVuPSIyMDE0LTAzLTA1VDIwOjAyOjM0LTA1OjAwIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBBZG9iZSBNZWRpYSBFbmNvZGVyIENDIChNYWNpbnRvc2gpIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvbWV0YWRhdGEiLz4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MGFiMTlhNzgtNmYzMi00YmU4LTg1NWYtYjY5MzkxODI2MGFlIgogICAgICBzdEV2dDp3aGVuPSIyMDE0LTAzLTEyVDE0OjU3OjUxLTA0OjAwIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBBZG9iZSBNZWRpYSBFbmNvZGVyIENDIChNYWNpbnRvc2gpIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvIi8+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InNhdmVkIgogICAgICBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjliNDFjYTUzLTUzOGItNGNlYi05N2I3LWZhNWMyN2NhM2ZmZCIKICAgICAgc3RFdnQ6d2hlbj0iMjAxNC0wMy0xMlQxNDo1Nzo1MS0wNDowMCIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgQWRvYmUgTWVkaWEgRW5jb2RlciBDQyAoTWFjaW50b3NoKSIKICAgICAgc3RFdnQ6Y2hhbmdlZD0iL21ldGFkYXRhIi8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICAgPHhtcE1NOkRlcml2ZWRGcm9tCiAgICBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjNmOTczMjUwLTAzZjktNGI4YS04MjQ1LTgwN2NkMzI3ODY0MCIKICAgIHN0UmVmOmRvY3VtZW50SUQ9IjI5ZTA5MTU5LTc1NWYtZjA0MC0zZjRlLWFmMzYwMDAwMDA0OSIKICAgIHN0UmVmOm9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo3MGQ4OTFjYi0xN2RkLTRjOGEtOGJhMS0xMDg0Mjk0NTBiODkiLz4KICAgPHhtcERNOnN0YXJ0VGltZWNvZGUKICAgIHhtcERNOnRpbWVGb3JtYXQ9IjI5OTdEcm9wVGltZWNvZGUiCiAgICB4bXBETTp0aW1lVmFsdWU9IjAwOzAwOzAwOzAwIi8+CiAgIDx4bXBETTphbHRUaW1lY29kZQogICAgeG1wRE06dGltZVZhbHVlPSIwMDswMDswMDswMCIKICAgIHhtcERNOnRpbWVGb3JtYXQ9IjI5OTdEcm9wVGltZWNvZGUiLz4KICAgPHhtcERNOmR1cmF0aW9uCiAgICB4bXBETTp2YWx1ZT0iNCIKICAgIHhtcERNOnNjYWxlPSIxMDAxLzMwMDAwIi8+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz4AVElUMwAAABgAAADVvrOky9iyxChzYy5jaGluYXouY29tKVRQVUIAAAAYAAAA1b6zpMvYssQoc2MuY2hpbmF6LmNvbSlXT0FSAAAAFwAA1b6zpMvYssQoc2MuY2hpbmF6LmNvbSlURU5DAAAAGAAAANW+s6TL2LLEKHNjLmNoaW5hei5jb20pVEFMQgAAABgAAADVvrOky9iyxChzYy5jaGluYXouY29tKVRZRVIAAAAFAAAAMjAyMVRQRTIAAAAYAAAA1b6zpMvYssQoc2MuY2hpbmF6LmNvbSlUSVQyAAAAGAAAANW+s6TL2LLEKHNjLmNoaW5hei5jb20pVENPTgAAABgAAADVvrOky9iyxChzYy5jaGluYXouY29tKUNPTU0AAAAcAAAAY2hpANW+s6TL2LLEKHNjLmNoaW5hei5jb20pVFBFMQAAABgAAADVvrOky9iyxChzYy5jaGluYXouY29tKQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/++BAAAALMWlEdTGAArstKI6mMABc7hdx2PeAA53C7jse8ACLqJIRIib7/wAAOAwTn7RIEh00uvfscEg8bAgOjJLJ7RIJlSQTObXr7/l/YcqdiWT7CWrOCZEOYA4VpgAwHqiQIAkHnXWddf9FizqUmcoscbEg8qdmZmv+j/0pzZ2vxe+wscbMz+x2fvzSlMpf6Odde/i9ffGFixzVjm3vjDmrHG17+LFmnB51169e/i+9Fjmr1/3v87e/0Xv/RYscptKTf5pSZ295osc04ckP90B0cPzeIjlg/gA7///Ef///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////xdRJCJETff+AABwGCc/aJAkOml179jgkHjYEB0ZJZPaJBMqSCZza9ff8v7DlTsSyfYS1ZwTIhzAHCtMAGA9USBAEg866zrr/osWdSkzlFjjYkHlTszM1/0f+lObO1+L32FjjZmf2Oz9+aUplL/Rzrr38Xr74wsWOasc298Yc1Y42vfxYs04POuvXr38X3osc1ev+9/nb3+i9/6LFjlNpSb/NKTO3vNFjmnDkh/ugOjh+bxEcsH8AHf//4jYXmLGKENzFBIppppq8ki4J+YJyCvj8L4JmXghZ2iuEkNMmamIAPx4cJPI5doiGOCpb3aOZ2nfhuagiItiMcvJkEFvv34SMY55rKOMklJgnAoNa/3RKHW1KBXN5HRnjnEUH1n/+GiTTaTcH5dKJc/0wwKyJ////tYZ9vV50a5wQI6jV6smhK1WLi+NU//+srVmNTqPwY8Nj1L2FnvH3vEVe3v/7////7Ym2PatjKN0xLDaXNsKc5BXqtsdOJhSqtRx1ZMPSzHd//////////////mLelpsb+8bz9fW/////5L39YObSwIbC8xYxQhuYoJFNNNNXkkXBPzBOQV8fhfBMy8ELO0VwkhpkzUxAB+PDhJ5HLtEQxwVLe7RzO078NzUERFsRjl5Mggt9+/CRjHPNZRxkkpME4FBrX+6JQ62pQK5vI6M8c4ig+s//w0SabSbg/LpRLn+mGBWRP///9rDPt6vOjXOCBHUavVk0JWqxcXxqn//1lasxqdR+DHhsepews94+94ir29//f////bE2x7VsZRumJYbS5thTnIK9VtjpxMKVVqOOrJh6WY7v/////////////8xb0tNjf3jefr63/////Je/rBzaWBD/++BAAAALFldY7mMAAMvK6x3MYAAcFZ9VvYwAO4Kz6rexgAcpEsvFsmIlkslkstqRVMdYNfBgIuC0mkuseE7rjMlU4UbaY16Lqea0rC15rzWYOdp/puMWqGG41PF3oYkhdWvq5LqbOt4OEPGCiVio3UUHTVaNZ6u40wIS2zsQG9S+ZTnhqrS97+vyLyFqVbEggaRONkO62Vf8f3+Xd69wpuW4tZh1wkNqb/y/eP465////vUqazIW5O06MFNep8pT+suZZb/X///vX//P9I1TJQJlz7L5QCsCi75M+LZF6otSlXJVItTkPEw4JPlRUFf////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8pEsvFsmIlkslkstqRVMdYNfBgIuC0mkuseE7rjMlU4UbaY16Lqea0rC15rzWYOdp/puMWqGG41PF3oYkhdWvq5LqbOt4OEPGCiVio3UUHTVaNZ6u40wIS2zsQG9S+ZTnhqrS97+vyLyFqVbEggaRONkO62Vf8f3+Xd69wpuW4tZh1wkNqb/y/eP465////vUqazIW5O06MFNep8pT+suZZb/X///vX//P9I1TJQJlz7L5QCsCi75M+LZF6otSlXJVItTkPEw4JPlRUFSSUm243G2k4n8mc05ToBKL/IPM2lrcZCslUMUXytlShr7rLRch5l4yas+t2jootfU14gmau1mGXlmpG/LlSe9AVlQ0vCBBl6WuoMqWy6ZdmclNSG3dpKtDEXFlN9yYpNyaxDUP3s3Fp357Ua0u6Fy5d0AwmUS2UxmIOVP3J7uVqrO4zNmlw5lbrWrmr8RlztX4e5GZU/z/a3j+WVLjjWr3rku7hlrUZucuV5qNQ9cm525DW3Sdaaxna1rvKupVjO25VWtTU3hWl0ajVNVlMVu5dpbMamcaWGY1Kp7mseVqZCSSk23G420nE/kzmnKdAJRf5B5m0tbjIVkqhii+VsqUNfdZaLkPMvGTVn1u0dFFr6mvEEzV2swy8s1I35cqT3oCsqGl4QIMvS11BlS2XTLszkpqQ27tJVoYi4spvuTFJuTWIah+9m4tO/PajWl3QuXLugGEyiWymMxByp+5PdytVZ3GZs0uHMrda1c1fiMudq/D3IzKn+f7W8fyypcca1e9cl3cMtajNzlyvNRqHrk3O3Ia26TrTWM7Wtd5V1KsZ23Kq1qam8K0ujUapqspit3LtLZjUzjSwzGpVPc1jytTIT/++BAAAAP+mXP6e97bv3Muf09723PLMMjrGGLYeWYZHWMMWwMlJuSNyNpJizBFHaWwBJJkBiPJUiSklHqV47gEokIpJPkIH2EZWRNQbJqokTUuSqOI5WAq0aX4/lCrVS+J0fxCx6WVDiQhcvzuABwvgNUvD9DUafx1tB5k5PUI8cyDbxbTpQ1gseyuQpmQ5CorUPphVhbkKKEsMZ+xGAUzi5xnuoUGWLlUvs1xaj6NWWumtlrBYSUqhyVrDNTONyvf8RcWxebxvJukbEKzE5WtVhcW6+dPbY38b/alFd7EzTMSCrcY76Nu1Yr2L4Us3+JYtR0Vf//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wyUm5I3I2kmLMEUdpbAEkmQGI8lSJKSUepXjuASiQikk+QgfYRlZE1BsmqiRNS5Ko4jlYCrRpfj+UKtVL4nR/ELHpZUOJCFy/O4AHC+A1S8P0NRp/HW0HmTk9QjxzINvFtOlDWCx7K5CmZDkKitQ+mFWFuQooSwxn7EYBTOLnGe6hQZYuVS+zXFqPo1Za6a2WsFhJSqHJWsM1M43K9/xFxbF5vG8m6RsQrMTla1WFxbr509tjfxv9qUV3sTNMxIKtxjvo27VivYvhSzf4li1HRV///////////////////////////////////////////////////////////8JN3bWyRpAApBIlhYabwvYQjLfpdpNpgAgBdhI4UGoKichEqJgKPL+sid6hf2I2s7sMxmN5wC6tJjOXMTo2IqhONN3i6VW3R9bZraNvd4kutWpzHZ65dLtZttaQNUYz896BdZ62KP+14pLN6e23Q03DqVPf/S/26TdYSbu2tkjSABSCRLCw03hewhGW/S7SbTABAC7CRwoNQVE5CJUTAUeX9ZE71C/sRtZ3YZjMbzgF1aTGcuYnRsRVCcabvF0qtuj62zW0be7xJdatTmOz1y6Xazba0gaoxn570C6z1sUf9rxSWb09tuhpuHUqe/+l/t0m6z/++BAAAAP+T5Iaw9C4X8nyQ1h6FwHHFsZjDBHCOOLYzGGCOERq2WySSRttplIHJrIChrKZaJUYVVLTR5gbbpaH8fpUGCYBlEGMfEPDS5wnzapm5agYYomM10tbdEmgSFqDwPgbSdanIOC1iEHrCLuMDtYHMh6qPGWvX1VlbVPxcV/V//cf9xH5KFUVuCrhUWWCrziHzyjxGdqW5JmPdLJrOOwaK//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////iNWy2SSSNttMpA5NZAUNZTLRKjCqpaaPMDbdLQ/j9KgwTAMogxj4h4aXOE+bVM3LUDDFExmulrbok0CQtQeB8DaTrU5BwWsQg9YRdxgdrA5kPVR4y16+qsrap+Liv6v/7j/uI/JQqitwVcKiywVecQ+eUeIztS3JMx7pZNZx2DRX///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////bjTTJTKgAw5pKlKHAM0ITKvkLq1YHx7G6JJyp6a2nBOFHNFVQCsOGiiAwQZUJqN/poVF8bw76ta43GmmSmVABhzSVKUOAZoQmVfIXVqwPj2N0STlT01tOCcKOaKqgFYcNFEBggyoTUb/TQqL43h31a1z/++BAAAAP/ABFhQAACBoACLCgAAEdaiVP+PmQC61Eqf8fMgH////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8MaMLUMUrM2MFConHI7HZHI2VWSAug+zjGOLAdZRw2yot5YycHcOYQ1MV8ZcaI4zYlSJlwZ8hhYHCmaGZuKXIoVJQSRJ/d2JIVoLgKAz4zAoAWQGry+7+NMZgfYyZHiC4cGDYgMsEeAIQDQJ33x7HeTBOGxomAVQBUYuYYwiQ5ArTZrbFxkyoTBUTdMMFlIliACAggAAUcQetvtfNXQUya3QPkwMuUTcdZEz44ERlxbBSfZDbbaaEUKhfJxZuX0mL6adQeuFzgyiZFxlyuXzcUvNS+JQHZ////7f///9Y5g5hUN1IOgyabm4eIwxowtQxSszYwUKiccjsdkcjZVZIC6D7OMY4sB1lHDbKi3ljJwdw5hDUxXxlxojjNiVImXBnyGFgcKZoZm4pcihUlBJEn93YkhWguAoDPjMCgBZAavL7v40xmB9jJkeILhwYNiAywR4AhANAnffHsd5ME4bGiYBVAFRi5hjCJDkCtNmtsXGTKhMFRN0wwWUiWIAICCAABRxB62+181dBTJrdA+TAy5RNx1kTPjgRGXFsFJ9kNttpoRQqF8nFm5fSYvpp1B64XODKJkXGXK5fNxS81L4lAdn////t////1jmDmFQ3Ug6DJpubh4j/++BAAAALFWzWdj3gAMotms7HvAAcMYMvPYyAK4YwZeexkAU0YnlElWYkcQFVVWqatCRgkKHEPQqSYvRwqMnBmjxMMkS7FxNMn6Fkgci4RWxlTNXadOVh2pToUBCIlYzXuNRqLe4pNOKlTMTeXo/WXHbuaMJeXLcm1Ku3Sl2htfnO8Ky6pVsLntTt2bT2z//8lST0qTkQjDSz1UrKfsOLAis0H+vrq38RwiqN+uWplix49s0s+/zqC9//+v/8u52Y6lloiNimSymfW0fySrBxBZVLiM12zXHr9//////1tGjemL73rDcH3/sPPwaI///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////5oxPKJKsxI4gKqqtU1aEjBIUOIehUkxejhUZODNHiYZIl2LiaZP0LJA5FwitjKmau06crDtSnQoCERKxmvcajUW9xSacVKmYm8vR+suO3c0YS8uW5NqVdulLtDa/Od4Vl1SrYXPanbs2ntn//5KknpUnIhGGlnqpWU/YcWBFZoP9fXVv4jhFUb9ctTLFjx7ZpZ9/nUF7//9f/5dzsx1LLREbFMllM+to/klWDiCyqXEZrtmuPX7//////raNG9MX3vWG4Pv/Yefg0RWWb9mvrVucJ/2GpiBdMvWaIgwS4jO25NqwEyFTMZs/7aMjbI2RH5nT/MPgeYcGQKUtcceca7DNhZLsw4giMUYwzDLEXK3s4ypl0Wa0waUJtnsaXxW4oMo3By0W0cp1ZXNyaXrtTlX42KH52ljshm7aYqRyXS8qNynSc7GM1GnQc+7iQ3ecFpqt0fUy6uaXNahUndqNTMOymdmoelMegb5TYzx3Kd50XdWrW5m1lEp7Va/Vpb+qtLZy3zWNy1+VW9KZTlj81Lrt7LKrS4483V1+ONmUxnXzVrKVU25DjcsUTJZZZv2a+tW5wn/YamIF0y9ZoiDBLiM7bk2rATIVMxmz/toyNsjZEfmdP8w+B5hwZApS1xx5xrsM2FkuzDiCIxRjDMMsRcrezjKmXRZrTBpQm2expfFbigyjcHLRbRynVlc3Jpeu1OVfjYofnaWOyGbtpipHJdLyo3KdJzsYzUadBz7uJDd5wWmq3R9TLq5pc1qFSd2o1Mw7KZ2ah6Ux6BvlNjPHcp3nRd1atbmbWUSntVr9Wlv6q0tnLfNY3LX5Vb0plOWPzUuu3ssqtLjjzdXX442ZTGdfNWspVTbkONyxRMlj/++BAAAAP+TZG6xjC0ZymyN1jGFoHzHMZBgSpAFeOYyDAlSBQlJyNtuNIkoTkUUFmcCMQdIKBQJBAkeS2qtoQmncFlqyQMWGYaZSmLAtecd6AZLepcbOL6vy0qKP1QsNdXvGVMSZzJp6rhKWcxmAntBB4ZkMTRNQAwI1pYVsCVzYZ2lmYze+rZwyrV39fagn7Ea7q/j+WOWdJPb/uXbOGWXfqmnogYHYdWwO6Z5NXKuyKV/uh1Z2qv1f/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////qEpORttxpElCciigszgRiDpBQKBIIEjyW1VtCE07gstWSBiwzDTKUxYFrzjvQDJb1LjZxfV+WlRR+qFhrq94ypiTOZNPVcJSzmMwE9oIPDMhiaJqAGBGtLCtgSubDO0szGb31bOGVau/r7UE/YjXdX8fyxyzpJ7f9y7Zwyy79U09EDA7Dq2B3TPJq5V2RSv90OrO1V+r//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////+pUAEBMBJVhlHASnphJ//////+JAY3//////////////////////////////////////////////////////UqACAmAkqwyjgJT0wk///////EgMb/++BAAAAP/ABLgAAACZyACXAAAAEKgAEuAAAAIAAAJcAAAAT/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////++BAAAAP/ABLgAAACZyACXAAAAEKgAEuAAAAIAAAJcAAAAT///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9UQUfVvrOky9iyxChzYy5jaGluYXouY29tKSAgICAgICDVvrOky9iyxChzYy5jaGluYXouY29tKSAgICAgICDVvrOky9iyxChzYy5jaGluYXouY29tKSAgICAgICAyMDIx1b6zpMvYssQoc2MuY2hpbmF6LmNvbSkgICAgICAgDA==
        """
        try:
            sound_data = base64.b64decode(sound_base64)
            sound_file = io.BytesIO(sound_data)
            self.key_sound = pygame.mixer.Sound(sound_file)
        except Exception as e:
            print(f"无法加载内置音效: {e}")
            self.key_sound = pygame.mixer.Sound(buffer=bytearray(0))

    def setup_ui(self):
        """设置用户界面"""
        main_frame = ttk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True)

        style = ttk.Style()
        style.theme_use('default')
        style.configure("Horizontal.TProgressbar",
                        troughcolor="#eeeeee",
                        background="#4CAF50",
                        thickness=20)
        # 控制按钮框架
        control_frame = ttk.Frame(main_frame)
        control_frame.pack(fill=tk.X, pady=(0, 40))

        # 添加设置按钮
        ttk.Button(
            control_frame,
            text="设置",
            command=self.open_settings_dialog,
            style="Accent.TButton"  # 使用强调按钮样式
        ).pack(side=tk.LEFT, padx=20)

        # 添加字符统计显示区域
        self.stats_frame = ttk.Frame(main_frame)
        self.stats_frame.pack(fill=tk.X, pady=(10, 20))

        self.char_stats_label = tk.Text(
            self.stats_frame,
            height=6,  # 增加高度
            wrap=tk.WORD,
            font=("Consolas", 12),  # 增大字体
            state=tk.DISABLED,
            bg="#f0f0f0",
            relief=tk.FLAT
        )
        self.char_stats_label.pack(fill=tk.BOTH, expand=True)

        # 控制按钮
        ttk.Button(
            control_frame,
            text="加载文本",
            command=self.load_text_file,
            style="Accent.TButton"  # 使用强调按钮样式
        ).pack(side=tk.LEFT, padx=20)

        ttk.Button(
            control_frame,
            text="清除统计",
            command=self.clear_stats,
            style="Accent.TButton"  # 使用强调按钮样式
        ).pack(side=tk.LEFT, padx=20)

        # 新增：重置最快记录按钮
        ttk.Button(
            control_frame,
            text="重置最快记录",
            command=self.reset_fastest_record,
            style="Accent.TButton"  # 使用强调按钮样式
        ).pack(side=tk.LEFT, padx=20)

        # 新增：显示最快记录的标签
        self.fastest_record_label = ttk.Label(
            control_frame,
            text="最快记录: 未有记录",
            font=("Microsoft YaHei", 16),  # 增大字体
            foreground="#FF5722"  # 使用醒目的橙色
        )
        self.fastest_record_label.pack(side=tk.LEFT, padx=20)

        # 状态信息
        self.status_label = ttk.Label(
            main_frame,
            text="按下键盘开始练习...",
            font=("Microsoft YaHei", 18),  # 增大字体
            foreground="#FF5722"  # 使用醒目的橙色
        )
        self.status_label.pack(pady=(0, 20))

        # 文本预览区域
        self.text_preview = tk.Text(
            main_frame,
            height=20,
            wrap=tk.WORD,
            font=("Consolas", 18),  # 增大字体
            state=tk.DISABLED,
            bg="#ffffff",
            relief=tk.SOLID,
            borderwidth=2
        )
        self.text_preview.pack(fill=tk.BOTH, expand=True, padx=20, pady=(0, 20))

        # 设置文本标签
        # self.text_preview.tag_config("current_char", underline=True, underlinefg="red", overstrike=True)
        self.text_preview.tag_config("current_char", background="pink")
        self.text_preview.tag_config("correct", foreground="green", font=("Consolas", 18, "bold"))
        self.text_preview.tag_config("wrong", foreground="red", font=("Consolas", 18, "bold"))
        self.text_preview.tag_config("flash", background="red")
        self.text_preview.tag_config("error_highlight", background="yellow")
        # 设置文本标签（新增comment标签）
        self.text_preview.tag_config("comment", foreground="green", font=("Consolas", 14))

        # 统计信息
        stats_frame = ttk.Frame(main_frame)
        stats_frame.pack(fill=tk.X, pady=(0, 20))

        # 修改原有统计标签（移除进度百分比）
        self.stats_label = ttk.Label(
            stats_frame,
            text="正确: 0 | 错误: 0",
            font=("Microsoft YaHei", 16),  # 增大字体
            foreground="#333333"  # 深色字体
        )
        self.stats_label.pack()

        # 初始化文本显示
        self.update_text_preview()
        self.start_blinking()

        # 初始化最快记录显示
        self.update_fastest_record_label()

    def load_and_display_char_stats(self):
        """加载并显示字符统计信息"""
        # 从数据库获取所有字符的统计
        self.cursor.execute('''
            SELECT 
                es.char, 
                es.count as error_count,
                cs.total_input,
                CASE 
                    WHEN cs.total_input > 0 THEN ROUND((es.count * 100.0 / cs.total_input), 1)
                    ELSE 0 
                END as error_rate
            FROM error_stats es
            JOIN char_stats cs ON es.char = cs.char
            ORDER BY error_rate DESC
        ''')

        stats = self.cursor.fetchall()

        # 准备显示内容
        stats_text = "字符统计（错误率从高到低）：\n"
        stats_text += "{:<5} {:<8} {:<10} {:<10}\n".format("字符", "错误次数", "总输入次数", "错误率(%)")
        stats_text += "-" * 40 + "\n"

        for char, error_count, total_input, error_rate in stats:
            stats_text += "{:<7} {:<12} {:<14} {:<10}\n".format(
                repr(char)[1:-1],  # 去掉引号
                error_count,
                total_input,
                error_rate
            )

        # 更新显示
        self.char_stats_label.config(state=tk.NORMAL)
        self.char_stats_label.delete(1.0, tk.END)
        self.char_stats_label.insert(tk.END, stats_text)
        self.char_stats_label.config(state=tk.DISABLED)

    def start_blinking(self):
        """开始闪烁当前字符的下划线"""
        if self.blink_id:
            self.root.after_cancel(self.blink_id)

        if self.current_pos < len(self.practice_text):
            self.blink_state = not self.blink_state
            if self.blink_state:
                self.text_preview.tag_config("current_char", underline=True, underlinefg="red")
            else:
                self.text_preview.tag_config("current_char", underline=False)

            self.blink_id = self.root.after(500, self.start_blinking)

    def flash_error(self):
        """显示错误闪烁"""
        if self.flash_id:
            self.root.after_cancel(self.flash_id)
            self.text_preview.tag_remove("flash", "1.0", tk.END)

        start_index = f"1.0 + {self.current_pos} chars"
        self.text_preview.tag_add("flash", start_index, f"{start_index} + 1 chars")
        self.flash_id = self.root.after(1000, lambda: self.text_preview.tag_remove("flash", "1.0", tk.END))

    def reset_practice(self):
        """重置练习状态"""
        self.current_pos = 0  # 重置当前字符位置
        self.correct_count = 0  # 重置正确计数
        self.wrong_count = 0  # 重置错误计数
        self.error_stats = defaultdict(int)  # 重置错误统计
        self.update_text_preview()  # 更新文本预览
        self.status_label.config(text="按下键盘开始练习...", foreground="gray")  # 重置状态标签

    def handle_key_press(self, event):
        """处理键盘按键"""
        self.play_key_sound(event)

        if self.current_pos == len(self.practice_text):
            self.status_label.config(text="练习完成！", foreground="green")
            end_time = time.time()
            elapsed_time = end_time - self.start_time
            seconds = int(elapsed_time)
            print(f"本次打字用了 {seconds} 秒")
            self.cursor.execute('INSERT INTO typing_records (elapsed_time) VALUES (?)', (elapsed_time,))
            self.conn.commit()

            fastest_time = self.get_fastest_record()
            if fastest_time is None or elapsed_time <= fastest_time:
                if self.enable_word_reading:
                    pygame.mixer.music.load("成功.mp3")
                    pygame.mixer.music.play()
                    time.sleep(5)
                threading.Thread(target=self.speak_word, args=(f"本次打字用了 {seconds} 秒",)).start()
                threading.Thread(target=self.speak_word, args=("你创造了新的最快记录！",)).start()
            else:
                diff_seconds = int(abs(fastest_time - elapsed_time))
                minutes = diff_seconds // 60
                seconds = diff_seconds % 60
                threading.Thread(target=self.speak_word, args=(f"本次打字用了 {seconds} 秒",)).start()
                threading.Thread(target=self.speak_word, args=(f"你比最快记录慢了 {minutes} 分 {seconds} 秒",)).start()

            self.update_fastest_record_label()
            self.reset_practice()
            return

        # 跳过注释行
        if self.practice_text[self.current_pos] == '#':
            self.current_pos = self.skip_comment_line()
            self.update_text_preview()
            return

        target_char = self.practice_text[self.current_pos]
        if target_char in self.skip_chars:
            self.current_pos += 1
            self.update_text_preview()
            return

        self.save_char_input(target_char)

        if event.char and event.char == target_char:
            self.mark_correct()
        elif event.char:
            self.mark_wrong(target_char)

        self.load_and_display_char_stats()
        self.update_text_preview()

    # 新增辅助方法
    def skip_comment_line(self):
        """跳过注释行"""
        while self.current_pos < len(self.practice_text) and self.practice_text[self.current_pos] != '\n':
            self.current_pos += 1
        if self.current_pos < len(self.practice_text) and self.practice_text[self.current_pos] == '\n':
            self.current_pos += 1
        return self.current_pos

    def get_fastest_record(self):
        """获取最快记录的时间"""
        self.cursor.execute('SELECT MIN(elapsed_time) FROM typing_records')
        fastest_time = self.cursor.fetchone()[0]
        return fastest_time

    def mark_correct(self):
        """标记为正确输入"""
        start_index = f"1.0 + {self.current_pos} chars"
        self.text_preview.tag_add("correct", start_index, f"{start_index} + 1 chars")
        self.correct_count += 1
        self.current_pos += 1
        self.status_label.config(text="正确！继续...", foreground="green")

        # 检查是否输入了一个完整的单词
        if self.current_pos < len(self.practice_text) and self.practice_text[self.current_pos] in self.skip_chars:
            word_end = self.current_pos
            word_start = word_end
            while word_start > 0 and self.practice_text[word_start - 1] not in self.skip_chars:
                word_start -= 1
            word = self.practice_text[word_start:word_end]
            threading.Thread(target=self.speak_word, args=(word,)).start()

            while self.current_pos < len(self.practice_text) and self.practice_text[self.current_pos] in self.skip_chars:
                self.current_pos += 1

            if self.current_pos >= len(self.practice_text):
                self.status_label.config(text="练习完成！", foreground="green")

    def mark_wrong(self, target_char):
        """标记为错误输入"""
        self.error_stats[target_char] += 1
        self.save_error_stat(target_char)
        self.flash_error()

        start_index = f"1.0 + {self.current_pos} chars"
        self.text_preview.tag_add("wrong", start_index, f"{start_index} + 1 chars")
        self.wrong_count += 1
        self.status_label.config(text=f"错误！应该是 '{target_char}'", foreground="red")

    def play_key_sound(self, event):
        """播放按键音效"""
        if self.enable_key_sound and event.char and event.keysym not in ["BackSpace", "Delete", "Shift_L", "Shift_R", "Control_L", "Control_R"]:
            try:
                self.key_sound.play()
            except:
                pass

    def update_text_preview(self):
        """更新文本预览"""
        self.text_preview.config(state=tk.NORMAL)
        self.text_preview.delete(1.0, tk.END)
        self.text_preview.insert(tk.END, self.practice_text)

        # 高亮错误比例高的字符
        for i, char in enumerate(self.practice_text):
            if char not in self.skip_chars:
                error_rate = self.get_error_rate(char)
                if error_rate > 0.5:
                    start_index = f"1.0 + {i} chars"
                    self.text_preview.tag_add("error_highlight", start_index, f"{start_index} + 1 chars")

        # 高亮当前字符
        if self.current_pos < len(self.practice_text):
            while self.current_pos < len(self.practice_text) and self.practice_text[self.current_pos] in self.skip_chars:
                self.current_pos += 1
            if self.current_pos < len(self.practice_text):
                start_index = f"1.0 + {self.current_pos} chars"
                self.text_preview.tag_add("current_char", start_index, f"{start_index} + 1 chars")
                self.text_preview.see(start_index)

        # 标记注释行
        lines = self.practice_text.split('\n')
        line_start = 0
        for line in lines:
            if line.strip().startswith('#'):
                start_index = f"1.0 + {line_start} chars"
                end_index = f"{start_index} + {len(line)} chars"
                self.text_preview.tag_add("comment", start_index, end_index)
            line_start += len(line) + 1

        self.text_preview.config(state=tk.DISABLED)
        self.stats_label.config(text=f"正确: {self.correct_count} | 错误: {self.wrong_count}")

    # 新增辅助方法
    def get_error_rate(self, char):
        """获取字符的错误率"""
        error_count, total_input = self.get_char_stats(char)
        return error_count / total_input if total_input > 0 else 0

    def get_char_stats(self, char):
        """获取字符的错误次数和总输入次数"""
        self.cursor.execute('''
            SELECT 
                (SELECT count FROM error_stats WHERE char = ?),
                (SELECT total_input FROM char_stats WHERE char = ?)
        ''', (char, char))
        result = self.cursor.fetchone()
        return result[0] if result and result[0] is not None else 0, result[1] if result and result[1] is not None else 0

    def process_speech_queue(self):
        """处理语音任务队列"""
        while True:
            word = self.speech_queue.get()
            if word is None:  # 队列结束标志
                break
            self.engine.say(word)
            self.engine.runAndWait()

    def speak_word(self, word):
        """异步调用的函数,用于朗读单词"""
        if self.enable_word_reading:
            self.speech_queue.put(word)

    def load_text_file(self):
        """加载文本文件"""
        file_path = filedialog.askopenfilename(
            title="选择文本文件",
            filetypes=[("文本文件", "*.txt"), ("Python文件", "*.py"), ("所有文件", "*.*")]
        )

        if file_path:
            try:
                with open(file_path, "r", encoding="utf-8") as f:
                    self.practice_text = f.read()
                self.save_practice_text(self.practice_text)
                self.reset_practice()
                messagebox.showinfo("成功", "文本加载成功！")
                threading.Thread(target=self.speak_word, args=("文本加载成功",)).start()
                self.file_path = file_path  # 保存文件路径
            except Exception as e:
                messagebox.showerror("错误", f"无法加载文件:\n{str(e)}")

    def reset_fastest_record(self):
        """重置最快记录"""
        # 清空最快记录表
        self.cursor.execute('DELETE FROM typing_records')
        self.conn.commit()
        # 更新显示
        self.update_fastest_record_label()
        threading.Thread(target=self.speak_word, args=("最快记录已重置",)).start()

    def update_fastest_record_label(self):
        """更新最快记录标签"""
        self.cursor.execute('SELECT MIN(elapsed_time) FROM typing_records')
        fastest_time = self.cursor.fetchone()[0]
        if fastest_time is not None:
            minutes = int(fastest_time // 60)
            seconds = int(fastest_time % 60)
            self.fastest_record_label.config(text=f"最快记录: {minutes} 分 {seconds} 秒")
        else:
            self.fastest_record_label.config(text="最快记录: 未有记录")

    def open_settings_dialog(self):
        """打开设置对话框,允许用户修改提示语和声音配置"""
        def update_settings():
            new_prompt = entry.get()
            if new_prompt:
                self.start_prompt = new_prompt
                messagebox.showinfo("成功", "提示语已更新！")
                dialog.destroy()

                # 新增：将新提示语保存到数据库
                self.cursor.execute('''
                    INSERT OR REPLACE INTO settings (key, value)
                    VALUES (?, ?)
                ''', ("start_prompt", new_prompt))
                self.conn.commit()

            # 保存声音配置
            self.enable_key_sound = var_key_sound.get()
            self.enable_word_reading = var_word_reading.get()
            self.save_sound_settings()

        dialog = tk.Toplevel(self.root)
        dialog.title("设置")
        dialog.geometry("300x250")

        # 提示语设置
        label_prompt = ttk.Label(dialog, text="请输入新的提示语：")
        label_prompt.pack(pady=10)

        entry = ttk.Entry(dialog, width=30)
        entry.insert(0, self.start_prompt)
        entry.pack(pady=10)

        # 声音配置
        var_key_sound = tk.BooleanVar(value=self.enable_key_sound)
        var_word_reading = tk.BooleanVar(value=self.enable_word_reading)

        check_key_sound = ttk.Checkbutton(dialog, text="启用键盘音效", variable=var_key_sound)
        check_key_sound.pack(pady=5)

        check_word_reading = ttk.Checkbutton(dialog, text="启用语音音效", variable=var_word_reading)
        check_word_reading.pack(pady=5)

        save_button = ttk.Button(dialog, text="保存", command=update_settings)
        save_button.pack(pady=10)

    def save_sound_settings(self):
        """保存声音配置到数据库"""
        self.cursor.execute('''
            INSERT OR REPLACE INTO sound_settings (key, value)
            VALUES (?, ?)
        ''', ('enable_key_sound', int(self.enable_key_sound)))
        self.cursor.execute('''
            INSERT OR REPLACE INTO sound_settings (key, value)
            VALUES (?, ?)
        ''', ('enable_word_reading', int(self.enable_word_reading)))
        self.conn.commit()

    def toggle_fullscreen(self, event=None):
        """切换全屏模式"""
        self.root.attributes('-fullscreen', not self.root.attributes('-fullscreen'))

    def exit_fullscreen(self, event=None):
        """退出全屏模式"""
        self.root.attributes('-fullscreen', False)
        threading.Thread(target=self.speak_word, args=("退出全屏模式",)).start()

    def __del__(self):
        """析构函数,关闭数据库连接并清理语音队列"""
        self.conn.close()
        self.speech_queue.put(None)  # 结束语音处理线程
        self.speech_thread.join()

    def run(self):
        """运行应用程序"""
        self.root.mainloop()


if __name__ == "__main__":
    root = tk.Tk()
    app = FlashTypingApp(root)
    app.run()

# pyinstaller --onefile --noconsole --icon=E:\dev\code\python\code-kids-typing\打字游戏.ico  --name "格格打字 V 1.0.0"  E:\dev\code\python\code-kids-typing\英语打字.py
# pyinstaller --onefile --noconsole --icon=打字游戏.ico  --name "格格打字 V 2.0.0"  英语打字.py
